package com.hc.fdkforwarders.common.validation;



import com.hc.fdkforwarders.common.utils.StringUtils;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.compile;

/**
 * @Description 手机号校验
 * @Author mengweibin
 * @Date 2021-07-07 14:28
 */
@Target({ElementType.FIELD,ElementType.PARAMETER})
// 表示该注解可以用于什么地方，可能的ElementType参数有：
//CONSTRUCTOR：构造器的声明
//FIELD：域声明（包括enum实例）
//LOCAL_VARIABLE：局部变量声明
//METHOD：方法声明
//PACKAGE：包声明
//PARAMETER：参数声明
//TYPE：类、接口（包括注解类型）或enum声明
@Retention(RetentionPolicy.RUNTIME)
//表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括：
//SOURCE：注解将被编译器丢弃
//CLASS：注解在class文件中可用，但会被VM丢弃
//RUNTIME：VM将在运行期间保留注解，因此可以通过反射机制读取注解的信息。
@Constraint(validatedBy = Phone.PhoneValidator.class) // 指定自定义注解校验实现
public @interface Phone {

    String message() default "手机号格式不正确";

    /**
     * groups 为分组验证，不配置为Default组，
     * 这样可以根据不同的应用场景来定义一些group
     * 比如在执行save与update操作时，可能很多参数在update时就不是必须的了，
     * 这样就可以定义两个接口，分别表示Save.class组与Update.class组， 定义空接口即可
     * 在save操作时，使用@valid(group={Save.class})，这样就可以只对在Save.class标记的注解中进行约束验证
     */
    Class<?>[] groups() default {};


    /**
     * WTFWTFWTF????
     * payload这个参数
     * 应用并不多，可以通过它 来携带给验证器一些元数据信息
     * 比如自定义验证器时，验证对象可以是String、也可以是Optional<String>
     * 这时仅仅只用@NotNull 就无法正确验证了，这时候可以通过payload来标记一些需要特殊处理的操作
     */
    Class<? extends Payload>[] payload() default {};



    /**
     * Phone注解 校验器 实现
     * 验证器需要实现ConstraintValidator<U, V>, 其中 U为对应的注解类， V为被该注解标记的字段的类型(或其父类型)
     * <p>
     *
     * 注: 当项目启动后，会(懒加载)创建ConstraintValidator实例，在创建实例后会初始化调
     *     用{@link ConstraintValidator#initialize}方法。
     *     所以， 只有在第一次请求时，会走initialize方法， 后面的请求是不会走initialize方法的。
     *
     * 注: (懒加载)创建ConstraintValidator实例时， 会走缓存; 如果缓存中有，则直接使用相
     *     同的ConstraintValidator实例； 如果缓存中没有，那么会创建新的ConstraintValidator实例。
     *     由于缓存的key是能唯一定位的， 且 ConstraintValidator的实例属性只有在
     *     {@link ConstraintValidator#initialize}方法中才会写；在{@link ConstraintValidator#isValid}
     *     方法中只是读。
     *     所以不用担心线程安全问题。
     *
     */
    class PhoneValidator implements ConstraintValidator<Phone, String> {


        // 正则
        private Pattern pattern = compile("^1[3-9]\\d{9}$");

        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {

            if (StringUtils.isEmpty(value)) {
                return false;
            }

            return pattern.matcher(value).matches();
        }
    }

}
